]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Rework window / actor focus handling
authorJasper St. Pierre <jstpierre@mecheye.net>
Sat, 18 May 2013 04:18:13 +0000 (00:18 -0400)
committerJasper St. Pierre <jstpierre@mecheye.net>
Mon, 8 Jul 2013 21:05:16 +0000 (17:05 -0400)
The duality of the Clutter's key focus and mutter's window focus has long been
a problem for us in lots of case, and caused us to create large and complicated
hacks to get around the issue, including GrabHelper's focus grab model.

Instead of doing this, tie basic focus management into the core of gnome-shell,
instead of requiring complex "application-level" management to get it done
right.

Do this by making sure that only one of an actor or window can be focused at
the same time, and apply the appropriate logic to drop one or the other,
reactively.

Modals are considered a special case, as we grab all keyboard events, but at
the X level, the client window still has focus. Make sure to not do any input
synchronization when we have a modal.

At the same time, remove the FOCUSED input mode, as it's no longer necessary.

https://bugzilla.gnome.org/show_bug.cgi?id=700735

js/ui/ctrlAltTab.js
js/ui/layout.js
js/ui/panel.js
src/shell-global.c
src/shell-global.h

index edfd6d3cd7d2db4eb1bf6ed9e12c4fc2f6f713ed..e5229090521ebea70ee6c441d224d2880a135865 100644 (file)
@@ -58,14 +58,10 @@ const CtrlAltTabManager = new Lang.Class({
     },
 
     focusGroup: function(item, timestamp) {
-        if (item.focusCallback) {
+        if (item.focusCallback)
             item.focusCallback(timestamp);
-        } else {
-            if (global.stage_input_mode == Shell.StageInputMode.NORMAL)
-                global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
-
+        else
             item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
-        }
     },
 
     // Sort the items into a consistent order; panel first, tray last,
@@ -136,8 +132,6 @@ const CtrlAltTabManager = new Lang.Class({
     },
 
     _focusWindows: function(timestamp) {
-        global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
-        global.stage.key_focus = null;
         global.screen.focus_default_window(timestamp);
     }
 });
index 2444a2ab45e30990c20055e7c66c5ee1d61d5e48..e8dcf1d1156da27eb84e39f52588ea2efc4f2fcc 100644 (file)
@@ -528,17 +528,10 @@ const LayoutManager = new Lang.Class({
     get focusIndex() {
         let i = Main.layoutManager.primaryIndex;
 
-        if (global.stage_input_mode == Shell.StageInputMode.FOCUSED ||
-            global.stage_input_mode == Shell.StageInputMode.FULLSCREEN) {
-            let focusActor = global.stage.key_focus;
-            if (focusActor)
-                i = this.findIndexForActor(focusActor);
-        } else {
-            let focusWindow = global.display.focus_window;
-            if (focusWindow)
-                i = focusWindow.get_monitor();
-        }
-
+        if (global.stage.key_focus != null)
+            i = this.findIndexForActor(global.stage.key_focus);
+        else if (global.display.focus_window != null)
+            i = global.display.focus_window.get_monitor();
         return i;
     },
 
index 729a57409b8757ae81c71b34e616d79f2dab73a1..795775f3de2aff5125d3437a26eceeac115a5c5d 100644 (file)
@@ -449,7 +449,7 @@ const AppMenuButton = new Lang.Class({
             // If the app has just lost focus to the panel, pretend
             // nothing happened; otherwise you can't keynav to the
             // app menu.
-            if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
+            if (global.stage.key_focus != null)
                 return;
         }
         this._sync();
index 4a82b953322d134874951a9d371d038334c80684..5629b622fa3d214d12aaa107a32d6593519aad2e 100644 (file)
@@ -94,6 +94,8 @@ struct _ShellGlobal {
   guint32 xdnd_timestamp;
 
   gint64 last_gc_end_time;
+
+  gboolean has_modal;
 };
 
 enum {
@@ -529,6 +531,18 @@ shell_global_get (void)
   return the_object;
 }
 
+static guint32
+get_current_time_maybe_roundtrip (ShellGlobal *global)
+{
+  guint32 time;
+
+  time = shell_global_get_current_time (global);
+  if (time != CurrentTime)
+    return time;
+
+  return meta_display_get_current_time_roundtrip (global->meta_display);
+}
+
 static void
 focus_window_changed (MetaDisplay *display,
                       GParamSpec  *param,
@@ -536,9 +550,58 @@ focus_window_changed (MetaDisplay *display,
 {
   ShellGlobal *global = user_data;
 
-  if (global->input_mode == SHELL_STAGE_INPUT_MODE_FOCUSED &&
-      meta_display_get_focus_window (display) != NULL)
-    shell_global_set_stage_input_mode (global, SHELL_STAGE_INPUT_MODE_NORMAL);
+  if (global->has_modal)
+    return;
+
+  /* If the stage window became unfocused, drop the key focus
+   * on Clutter's side. */
+  if (!meta_stage_is_focused (global->meta_screen))
+    clutter_stage_set_key_focus (global->stage, NULL);
+}
+
+static ClutterActor *
+get_key_focused_actor (ShellGlobal *global)
+{
+  ClutterActor *actor;
+
+  actor = clutter_stage_get_key_focus (global->stage);
+
+  /* If there's no explicit key focus, clutter_stage_get_key_focus()
+   * returns the stage. This is a terrible API. */
+  if (actor == CLUTTER_ACTOR (global->stage))
+    actor = NULL;
+
+  return actor;
+}
+
+static void
+sync_stage_window_focus (ShellGlobal *global)
+{
+  ClutterActor *actor;
+
+  if (global->has_modal)
+    return;
+
+  actor = get_key_focused_actor (global);
+
+  /* An actor got key focus and the stage needs to be focused. */
+  if (actor != NULL && !meta_stage_is_focused (global->meta_screen))
+    meta_focus_stage_window (global->meta_screen,
+                             get_current_time_maybe_roundtrip (global));
+
+  /* An actor dropped key focus. Focus the default window. */
+  else if (actor == NULL && meta_stage_is_focused (global->meta_screen))
+    meta_screen_focus_default_window (global->meta_screen,
+                                      get_current_time_maybe_roundtrip (global));
+}
+
+static void
+focus_actor_changed (ClutterStage *stage,
+                     GParamSpec   *param,
+                     gpointer      user_data)
+{
+  ShellGlobal *global = user_data;
+  sync_stage_window_focus (global);
 }
 
 /**
@@ -552,12 +615,6 @@ focus_window_changed (MetaDisplay *display,
  * passes through clicks outside that region. When it is
  * %SHELL_STAGE_INPUT_MODE_FULLSCREEN, the stage absorbs all input.
  *
- * When the input mode is %SHELL_STAGE_INPUT_MODE_FOCUSED, the pointer
- * is handled as with %SHELL_STAGE_INPUT_MODE_NORMAL, but additionally
- * the stage window has the keyboard focus. If the stage loses the
- * focus (eg, because the user clicked into a window) the input mode
- * will revert to %SHELL_STAGE_INPUT_MODE_NORMAL.
- *
  * Note that whenever a mutter-internal Gtk widget has a pointer grab,
  * the shell goes unresponsive and passes things to the underlying GTK+
  * widget to ensure that the widget gets any clicks it is expecting.
@@ -579,10 +636,6 @@ shell_global_set_stage_input_mode (ShellGlobal         *global,
   else
     meta_set_stage_input_region (screen, global->input_region);
 
-  if (mode == SHELL_STAGE_INPUT_MODE_FOCUSED)
-    meta_focus_stage_window (global->meta_screen,
-                             shell_global_get_current_time (global));
-
   if (mode != global->input_mode)
     {
       global->input_mode = mode;
@@ -956,6 +1009,8 @@ _shell_global_set_plugin (ShellGlobal *global,
                                "End of stage page repaint",
                                "");
 
+  g_signal_connect (global->stage, "notify::key-focus",
+                    G_CALLBACK (focus_actor_changed), global);
   g_signal_connect (global->meta_display, "notify::focus-window",
                     G_CALLBACK (focus_window_changed), global);
 
@@ -991,7 +1046,13 @@ shell_global_begin_modal (ShellGlobal       *global,
                           guint32           timestamp,
                           MetaModalOptions  options)
 {
-  return meta_plugin_begin_modal (global->plugin, global->stage_xwindow, None, options, timestamp);
+  /* Make it an error to call begin_modal while we already
+   * have a modal active. */
+  if (global->has_modal)
+    return FALSE;
+
+  global->has_modal = meta_plugin_begin_modal (global->plugin, global->stage_xwindow, None, options, timestamp);
+  return global->has_modal;
 }
 
 /**
@@ -1004,7 +1065,23 @@ void
 shell_global_end_modal (ShellGlobal *global,
                         guint32      timestamp)
 {
+  ClutterActor *actor;
+
+  if (!global->has_modal)
+    return;
+
   meta_plugin_end_modal (global->plugin, timestamp);
+  global->has_modal = FALSE;
+
+  /* If the stage window is unfocused, ensure that there's no
+   * actor focused on Clutter's side. */
+  if (!meta_stage_is_focused (global->meta_screen))
+    clutter_stage_set_key_focus (global->stage, NULL);
+
+  /* An actor dropped key focus. Focus the default window. */
+  else if (get_key_focused_actor (global) && meta_stage_is_focused (global->meta_screen))
+    meta_screen_focus_default_window (global->meta_screen,
+                                      get_current_time_maybe_roundtrip (global));
 }
 
 void
index c93cb1bdfc42b83fd8e22b344dcc5adfbceffd0e..7d61165c2442c2c5152fea087fcf5897e9d9c7af 100644 (file)
@@ -49,7 +49,6 @@ void     shell_global_freeze_keyboard        (ShellGlobal         *global,
 
 typedef enum {
   SHELL_STAGE_INPUT_MODE_NORMAL,
-  SHELL_STAGE_INPUT_MODE_FOCUSED,
   SHELL_STAGE_INPUT_MODE_FULLSCREEN
 } ShellStageInputMode;